/*
 * Copyright (c) 2016. 恒昌互联网运营中心.
 */
package com.credithc.eagleeye.plugins.webservice;
import com.alibaba.fastjson.JSON;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.agent.util.HostUtil;
import com.credithc.eagleeye.constants.EagleEyeConstant;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.exception.EagleEyeIgnoreException;
import com.credithc.eagleeye.plugins.annotation.EagleEyeIgnoreAnnotation;
import com.credithc.eagleeye.plugins.mvc.ContentCachingRequestWrapper;
import com.credithc.eagleeye.plugins.util.ClassUtil;
import com.credithc.eagleeye.plugins.util.IpUtil;
import com.credithc.eagleeye.plugins.util.LogUtil;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.FastJsonConvert;
import com.credithc.eagleeye.util.ThrowableToStringArray;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
/**
 * 鹰眼webservice切面
 * @since 1.0.9
 */
@SuppressWarnings("Duplicates")
@Aspect
public class EagleEyeWebServiceAspect {
    private transient static final Logger logger = LoggerFactory.getLogger(EagleEyeWebServiceAspect.class);
    private static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
    private static int MAX_REQUEST_BODY_LENGTH = 4000;

    public EagleEyeWebServiceAspect() {
        logger.info("EagleEyeWebServiceAspect init..");
    }

    /**
       要排除Feign上的RequestMapping等注解
     */
    @Pointcut("(@within(javax.jws.WebService))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.debug("EagleEyeWebServiceAspect around start...");
        Object resultObject = null;
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        EagleEyeIgnoreAnnotation ignoreAnnotation = method.getAnnotation(EagleEyeIgnoreAnnotation.class);
        if (ignoreAnnotation != null) {
            return joinPoint.proceed();
        }
        String methodName = method.getName();
        Object target = joinPoint.getTarget();
        String serviceName = target.getClass().getSimpleName();
        HttpServletRequest request = null;
        if(ClassUtil.webServiceCXFPresent){
            Message message = PhaseInterceptorChain.getCurrentMessage();
            request = (HttpServletRequest)message.get("HTTP.REQUEST");
        }

        EagleEyeThreadContext.init();
        TraceInfo traceInfo = new TraceInfo();
        String servletPath = "";
        String clientIp = "";
        if(request!=null) {
            String hcTraceId = request.getHeader(EagleEyeConstant.HC_TRACE_ID);
            if (StringUtils.isNotBlank(hcTraceId)) {
                traceInfo.setTraceId(hcTraceId);
            } else {
                //为了兼容credithc-framework-log模块中的trace
                String mdcTraceId = MDC.get(EagleEyeConstant.TRACE_ID);
                if (StringUtils.isNotBlank(mdcTraceId)) {
                    traceInfo.setTraceId(mdcTraceId);
                }
            }
            String hcRpcId = request.getHeader(EagleEyeConstant.HC_RPC_ID);
            if (hcRpcId != null && hcRpcId.length() > 0) {
                traceInfo.setRpcId(hcRpcId);
                traceInfo.addHierarchy();
                traceInfo.addSequenceNo();
            }
            servletPath = request.getServletPath();
            clientIp = IpUtil.getClientIp(request);
        }

        RpcTraceInfoVo rpcTraceInfoVo = new RpcTraceInfoVo();
        rpcTraceInfoVo.setRequestDateTime(ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date()));
        rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
        rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
        rpcTraceInfoVo.setRpcType(RpcTypeEnum.WEBSERVICE.getRpcName());
        rpcTraceInfoVo.setServiceCategory("cxf");
        rpcTraceInfoVo.setServiceName(serviceName);
        rpcTraceInfoVo.setMethodName(methodName);
        rpcTraceInfoVo.setServerHost(HostUtil.getIp() + ":" + servletPath);
        rpcTraceInfoVo.setClientHost(clientIp);

        if (EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMvcParameter() && request!=null ) {
            Map<String, String[]> parameterMap = request.getParameterMap();
            StringBuilder requestBodySb = new StringBuilder();
            if ((parameterMap != null && parameterMap.size() > 0)) {
                requestBodySb.append("parameterMap:");
                requestBodySb.append(FastJsonConvert.convertObjectToJSON(parameterMap));
                requestBodySb.append(";");
            }
            //读取body，但是必须在过滤器里替换
            try {
                if (request instanceof ContentCachingRequestWrapper) {
                    byte[] body = ((ContentCachingRequestWrapper) request).getContentAsByteArray();
                    if (body != null && body.length > 0) {
                        requestBodySb.append("body:");
                        requestBodySb.append(new String(body, "UTF-8"));
                        rpcTraceInfoVo.setRequestSize(requestBodySb.length());
                    }
                }
            } catch (IOException e) {
            }
            if (requestBodySb.length() <= MAX_REQUEST_BODY_LENGTH) {
                rpcTraceInfoVo.setRequestJson(requestBodySb.toString());
            } else {
                rpcTraceInfoVo.setRequestJson(requestBodySb.substring(0, MAX_REQUEST_BODY_LENGTH) + "..");
            }
        }

        traceInfo.addHierarchy();
        EagleEyeThreadContext.putTraceInfo(traceInfo);
        MDC.put("traceId", traceInfo.getTraceId());
        MDC.put("rpcId", traceInfo.getRpcId());
        MDC.put("clientIp", clientIp);

        long beginTime = System.currentTimeMillis();
        //执行调用
        try {
            resultObject = joinPoint.proceed();
            rpcTraceInfoVo.setRunTime(System.currentTimeMillis()-beginTime);
            rpcTraceInfoVo.setResult("OK");
            if (EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMvcParameter()) {
                rpcTraceInfoVo.setResponseJson(transform2JsonStr(resultObject));
            }
        } catch (Exception ex) {
            if (!(ex instanceof EagleEyeIgnoreException)) {
                //ERROR
                rpcTraceInfoVo.setResult("ERROR");
                rpcTraceInfoVo.setRunTime(System.currentTimeMillis()-beginTime);
                //异常的结果
                String[] traceStr = ThrowableToStringArray.convert(ex);
                StringBuilder builder = new StringBuilder();
                for(String trace : traceStr) {
                    builder.append(trace);
                    builder.append("\n");
                }
                rpcTraceInfoVo.setResponseJson(builder.toString());
            } else {
                //OK
                rpcTraceInfoVo.setResult("OK");
            }
            throw ex;
        } finally {
            EagleEyeThreadContext.removeTraceInfo();
            MDC.remove("traceId");
            MDC.remove("rpcId");
            MDC.remove("clientIp");
            EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
            LogUtil.printSplunkLog(rpcTraceInfoVo);
        }
        logger.debug("EagleEyeWebServiceAspect around stop...");
        return resultObject;
    }

    private String transform2JsonStr(Object argument) {
        if (ClassUtil.servletPresent && (argument instanceof ServletRequest || argument instanceof ServletResponse)) {
            return "...";
        }
        if (ClassUtil.springWebPresent && (argument instanceof MultipartFile)) {
            return "...";
        }
        try {
            return JSON.toJSONString(argument);
        } catch (Exception ex) {
        }
        return "";
    }
}
